{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "db8736a7-ed94-441c-9556-831fa57b5a10",
   "metadata": {},
   "source": [
    "# \"THE PRICE IS RIGHT\" Capstone Project\n",
    "\n",
    "This week - build a model that predicts how much something costs from a description, based on a scrape of Amazon data\n",
    "\n",
    "\n",
    "A model that can estimate how much something costs, from its description.\n",
    "\n",
    "# Order of play\n",
    "\n",
    "DAY 1: Data Curation  \n",
    "DAY 2: Data Pre-processing  \n",
    "DAY 3: Evaluation, Baselines, Traditional ML  \n",
    "DAY 4: Deep Learning and LLMs  \n",
    "DAY 5: Fine-tuning a Frontier Model  \n",
    "\n",
    "## DAY 4: Neural Networks and LLMs\n",
    "\n",
    "Today we'll work from Traditional ML to Neural Networks to Large Language Models!!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "681c717b-4c24-4ac3-a5f3-3c5881d6e70a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# imports\n",
    "\n",
    "import os\n",
    "from dotenv import load_dotenv\n",
    "from huggingface_hub import login\n",
    "from pricer.evaluator import evaluate\n",
    "from litellm import completion\n",
    "from pricer.items import Item\n",
    "import numpy as np\n",
    "from tqdm.notebook import tqdm\n",
    "import csv\n",
    "from sklearn.feature_extraction.text import HashingVectorizer\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.utils.data import DataLoader, TensorDataset\n",
    "from sklearn.model_selection import train_test_split\n",
    "from torch.optim.lr_scheduler import CosineAnnealingLR\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0e97c60",
   "metadata": {},
   "outputs": [],
   "source": [
    "LITE_MODE = False\n",
    "\n",
    "load_dotenv(override=True)\n",
    "hf_token = os.environ['HF_TOKEN']\n",
    "login(hf_token, add_to_git_credential=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a6b82a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "username = \"ed-donner\"\n",
    "dataset = f\"{username}/items_lite\" if LITE_MODE else f\"{username}/items_full\"\n",
    "\n",
    "train, val, test = Item.from_hub(dataset)\n",
    "\n",
    "print(f\"Loaded {len(train):,} training items, {len(val):,} validation items, {len(test):,} test items\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5856173-e68c-4975-a769-5f1736e227a5",
   "metadata": {},
   "source": [
    "# Before we look at the Artificial Neural Networks\n",
    "\n",
    "## There is a different kind of Neural Network we could consider"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3e81ee0-828a-4af8-9ccf-177af6c78a0c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write the test set to a CSV\n",
    "\n",
    "with open('human_in.csv', 'w', encoding=\"utf-8\") as csvfile:\n",
    "    writer = csv.writer(csvfile)\n",
    "    for t in test[:100]:\n",
    "        writer.writerow([t.summary, 0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aeafac31-1a10-4029-b190-030378e2fe01",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Read it back in\n",
    "\n",
    "human_predictions = []\n",
    "with open('human_out.csv', 'r', encoding=\"utf-8\") as csvfile:\n",
    "    reader = csv.reader(csvfile)\n",
    "    for row in reader:\n",
    "        human_predictions.append(float(row[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9709da2-28f0-419e-af71-4ef6c02246ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "def human_pricer(item):\n",
    "    idx = test.index(item)\n",
    "    return human_predictions[idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee165c4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "human = human_pricer(test[0])\n",
    "actual = test[0].price\n",
    "print(f\"Human predicted {human} for an item that actually costs {actual}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1ba3b3e-4b08-4f0b-9e51-ebb03a86085d",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(human_pricer, test, size=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4637d722",
   "metadata": {},
   "source": [
    "# And now - a vanilla Neural Network\n",
    "\n",
    "During the remainder of this course we will get deeper into how Neural Networks work, and how to train a neural network.\n",
    "\n",
    "This is just a sneak preview - let's make our own Neural Network, from scratch, using Pytorch.\n",
    "\n",
    "Use this to get intuition; it's not important to know all about Neural networks at this point.."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ffea8472",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Prepare our documents and prices\n",
    "\n",
    "y = np.array([float(item.price) for item in train])\n",
    "documents = [item.summary for item in train]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dad6063d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use the HashingVectorizer for a Bag of Words model\n",
    "# Using binary=True with the CountVectorizer makes \"one-hot vectors\"\n",
    "\n",
    "np.random.seed(42)\n",
    "vectorizer = HashingVectorizer(n_features=5000, stop_words='english', binary=True)\n",
    "X = vectorizer.fit_transform(documents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9eba2dd3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the neural network - here is Pytorch code to create a 8 layer neural network\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_size):\n",
    "        super(NeuralNetwork, self).__init__()\n",
    "        self.layer1 = nn.Linear(input_size, 128)\n",
    "        self.layer2 = nn.Linear(128, 64)\n",
    "        self.layer3 = nn.Linear(64, 64)\n",
    "        self.layer4 = nn.Linear(64, 64)\n",
    "        self.layer5 = nn.Linear(64, 64)\n",
    "        self.layer6 = nn.Linear(64, 64)\n",
    "        self.layer7 = nn.Linear(64, 64)\n",
    "        self.layer8 = nn.Linear(64, 1)\n",
    "        self.relu = nn.ReLU()\n",
    "\n",
    "    def forward(self, x):\n",
    "        output1 = self.relu(self.layer1(x))\n",
    "        output2 = self.relu(self.layer2(output1))\n",
    "        output3 = self.relu(self.layer3(output2))\n",
    "        output4 = self.relu(self.layer4(output3))\n",
    "        output5 = self.relu(self.layer5(output4))\n",
    "        output6 = self.relu(self.layer6(output5))\n",
    "        output7 = self.relu(self.layer7(output6))\n",
    "        output8 = self.layer8(output7)\n",
    "        return output8"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ebd5e774",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Convert data to PyTorch tensors\n",
    "X_train_tensor = torch.FloatTensor(X.toarray())\n",
    "y_train_tensor = torch.FloatTensor(y).unsqueeze(1)\n",
    "\n",
    "# Split the data into training and validation sets\n",
    "X_train, X_val, y_train, y_val = train_test_split(X_train_tensor, y_train_tensor, test_size=0.01, random_state=42)\n",
    "\n",
    "# Create the loader\n",
    "train_dataset = TensorDataset(X_train, y_train)\n",
    "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\n",
    "\n",
    "# Initialize the model\n",
    "input_size = X_train_tensor.shape[1]\n",
    "model = NeuralNetwork(input_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ae064c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "\n",
    "print(f\"Number of trainable parameters: {trainable_params:,}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e958f0a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define loss function and optimizer\n",
    "\n",
    "loss_function = nn.MSELoss()\n",
    "optimizer = optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# We will do 2 complete runs through the data\n",
    "\n",
    "EPOCHS = 2\n",
    "\n",
    "for epoch in range(EPOCHS):\n",
    "    model.train()\n",
    "    for batch_X, batch_y in tqdm(train_loader):\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        # The next 4 lines are the 4 stages of training: forward pass, loss calculation, backward pass, optimize\n",
    "        \n",
    "        outputs = model(batch_X)\n",
    "        loss = loss_function(outputs, batch_y)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        val_outputs = model(X_val)\n",
    "        val_loss = loss_function(val_outputs, y_val)\n",
    "\n",
    "    print(f'Epoch [{epoch+1}/{EPOCHS}], Train Loss: {loss.item():.3f}, Val Loss: {val_loss.item():.3f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "137bc638",
   "metadata": {},
   "outputs": [],
   "source": [
    "def neural_network(item):\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        vector = vectorizer.transform([item.summary])\n",
    "        vector = torch.FloatTensor(vector.toarray())\n",
    "        result = model(vector)[0].item()\n",
    "    return max(0, result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0de8c947",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(neural_network, test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "066fef03-8338-4526-9df3-89b649ad4f0a",
   "metadata": {},
   "source": [
    "# And now - to the frontier!\n",
    "\n",
    "Let's see how Frontier models do out of the box; no training, just inference based on their world knowledge.\n",
    "\n",
    "Tomorrow we will do some training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66ea68e8-ab1b-4f0d-aba4-a59574d8f85e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def messages_for(item):\n",
    "    message = f\"Estimate the price of this product. Respond with the price, no explanation\\n\\n{item.summary}\"\n",
    "    return [{\"role\": \"user\", \"content\": message}]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81ed25ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(test[0].summary)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ff92d61-0d27-4b0d-8b32-c9891016509b",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages_for(test[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "501a2a7a-69c8-451b-bbc0-398bcb9e1612",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The function for gpt-4.1-nano\n",
    "\n",
    "def gpt_4__1_nano(item):\n",
    "    response = completion(model=\"openai/gpt-4.1-nano\", messages=messages_for(item))\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "843d88b4-364a-431b-b48b-8a7c1f68b786",
   "metadata": {},
   "outputs": [],
   "source": [
    "gpt_4__1_nano(test[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d013e3da",
   "metadata": {},
   "outputs": [],
   "source": [
    "test[0].price"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36bdd2c9-1859-4f99-a09f-3ec83b845b30",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(gpt_4__1_nano, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29a8fde0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def claude_opus_4_5(item):\n",
    "    response = completion(model=\"anthropic/claude-opus-4-5\", messages=messages_for(item))\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e20266e",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(claude_opus_4_5, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81cf59ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "def gemini_3_pro_preview(item):\n",
    "    response = completion(model=\"gemini/gemini-3-pro-preview\", messages=messages_for(item), reasoning_effort='low')\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f56d533",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(gemini_3_pro_preview, test, size=50, workers=2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "feaba181",
   "metadata": {},
   "outputs": [],
   "source": [
    "def gemini_2__5_flash_lite(item):\n",
    "    response = completion(model=\"gemini/gemini-2.5-flash-lite\", messages=messages_for(item))\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23148639",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(gemini_2__5_flash_lite, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db07fbb1",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def grok_4__1_fast(item):\n",
    "    response = completion(model=\"xai/grok-4-1-fast-non-reasoning\", messages=messages_for(item), seed=42)\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f31279b",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(grok_4__1_fast, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bbcb3a7f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The function for gpt-5.1\n",
    "\n",
    "def gpt_5__1(item):\n",
    "    response = completion(model=\"gpt-5.1\", messages=messages_for(item), reasoning_effort='high', seed=42)\n",
    "    return response.choices[0].message.content\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e450b698",
   "metadata": {},
   "outputs": [],
   "source": [
    "evaluate(gpt_5__1, test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff9d1bac",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
